/*
 * rpmdb.c
 *
 *  Created on: Jan 1, 2010
 *      Author: jkeller
 */

#define _GNU_SOURCE 1
#include <linux/limits.h>
#include <arpa/inet.h>  /* For ntohl / ntohs */
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

#include "rpmdb.h"

#ifdef XML_UNICODE_WCHAR_T
    #include <wchar.h>
    #include <wctype.h>
    #define  scew_strtoul( nptr, endptr, base )  wcstoul( nptr, endptr, base )
    #define  scew_strtoull( nptr, endptr, base ) wcstoull( nptr, endptr, base )
#else
    #define  scew_strtoul( nptr, endptr, base )  strtoul( nptr, endptr, base )
    #define  scew_strtoull( nptr, endptr, base ) strtoull( nptr, endptr, base )
#endif

static void
parseChecksumString( const char * digestAsString, md5_byte_t * digest )
{
    unsigned long long upper=0;
    unsigned long long lower=0;
    char * checksum = strdup( digestAsString );

    memset( digest, 0, 16 );

    int len = strlen( checksum );
    if( len < 16 )
    {
        lower = strtoull( checksum, NULL, 16 );
        upper = 0;
    }
    else
    {
        char * lower_start = ( checksum+len )-16;
        lower = strtoull( lower_start, NULL, 16 );
        *lower_start = '\0';
        upper = strtoull( checksum, NULL, 16 );
    }

    for( unsigned int i=0; i<8; ++i )
    {
        digest[i]   = ( upper >> ( 8*( 7-i ) ) ) & 0xFFU;
        digest[i+8] = ( lower >> ( 8*( 7-i ) ) ) & 0xFFU;
    }

    free( checksum );

}

static void
parseChecksumXMLString( XML_Char * digestAsString, md5_byte_t * digest )
{
    unsigned long long upper=0;
    unsigned long long lower=0;
    XML_Char * checksum = scew_strdup( digestAsString );

    XML_Char upperString[32] = {0};

    memset( digest, 0, 16 );

    int len = scew_strlen( checksum );
    if( len < 16 )
    {
        lower = scew_strtoull( checksum, NULL, 16 );
        upper = 0;
    }
    else
    {
        char * lower_start = checksum+( len-16 )*sizeof( XML_Char );
        lower = strtoull( lower_start, NULL, 16 );
        scew_strncpy( upperString, checksum, len-16 );
        upper = strtoull( upperString, NULL, 16 );
    }

    for( int i=0; i<8; ++i )
    {
        digest[i]   = ( upper >> ( 8*( 7-i ) ) ) & 0xFFU;
        digest[i+8] = ( lower >> ( 8*( 7-i ) ) ) & 0xFFU;
    }

    free( checksum );

}

static
int
getLock( RpmDataBaseHandle database )
{
    assert( database->database_file );
    assert( database->root_path );

    struct stat buf;

    asprintf( &( database->lock_file ), "%s/%s.%d.lck", database->root_path, RPM_DATABASE_NAME, getpid() );

    link( database->database_file, database->lock_file );
    stat( database->lock_file, &buf );

    if( buf.st_nlink != 2 )
    {
        int rc = unlink( database->lock_file );
        if( rc )
        {
            perror( "minirpm" );
        }

        free( database->lock_file );
        database->lock_file = NULL;
        return( 1 );
    }

    return( 0 );
}

RpmDataBaseHandle
db_open( const char * root, int writeAccess, RpmStatus * retVal )
{
    if( retVal )
    {
        *retVal = rpmStsNoError;
    }

    scew_parser * parser = scew_parser_create();

    scew_parser_ignore_whitespaces( parser, SCEW_TRUE );

    char path_to_database[PATH_MAX] = {0};
    strcpy( path_to_database, root );
    strcat( path_to_database, "/" );
    strcat( path_to_database, RPM_DATABASE_NAME );

    scew_reader * reader = scew_reader_file_create( path_to_database );
    if( reader == NULL )
    {
        if( writeAccess == 0 )
        {
            /* Did not request write access.  So, we fail. */
            if( retVal )
            {
                *retVal = rpmStsDatabaseNotFound;
            }
            return( NULL );
        }
        /* No database there.  Create a new one. */
        RpmDataBaseHandle newHandle = (RpmDataBaseHandle)( malloc( sizeof( RpmDataBase ) ) );
        memset( newHandle, 0, sizeof( RpmDataBase ) );
        newHandle->root_path = strdup( root );
        asprintf( &newHandle->database_file, "%s/" RPM_DATABASE_NAME, root );

        int rc = RecursiveMakeDirectories( root );
        if( rc )
        {
            if( retVal )
            {
                *retVal = rpmStsWriteError;
            }
            db_close( newHandle );
        }

        FILE * emptydb = fopen( newHandle->database_file, "w" );
        if( emptydb == NULL )
        {
            if( retVal )
            {
                *retVal = rpmStsWriteError;
            }
            db_close( newHandle );
            return( NULL );
        }
        fprintf( emptydb, "\n" );
        fclose( emptydb );

        if( getLock( newHandle ) != 0 )
        {
            /*
             * Coudln't  get lock on new database
             */
            if( retVal )
            {
                *retVal = rpmStsDatabaseLockError;
            }
            db_close( newHandle );
            return( NULL );
        }
        newHandle->tree = scew_tree_create();
        scew_element * root = scew_tree_set_root( newHandle->tree, _XT( "minirpm" ) );

        scew_element_add_pair( root, _XT( "version" ), _XT( "<![CDATA[1.0]]>" ) );
        scew_element_add( root, _XT( "packages" ) );

        newHandle->writeAccess = 1;
        return( newHandle );
    }

    RpmDataBaseHandle newHandle = (RpmDataBaseHandle)( malloc( sizeof( RpmDataBase ) ) );
    memset( newHandle, 0, sizeof( RpmDataBase ) );
    asprintf( &newHandle->database_file, "%s/" RPM_DATABASE_NAME, root );
    if( writeAccess != 0 )
    {

        //if( getLock(newHandle) != 0)
        {
            /*
             * Coudln't  get lock on new database
             */
            if( retVal )
            {
                *retVal = rpmStsDatabaseLockError;
            }
            db_close( newHandle );
            return( NULL );
        }
        newHandle->writeAccess = 1;
    }

    scew_tree * tree= scew_parser_load( parser, reader );
    if( !tree )
    {
        scew_error code = scew_error_code();
        scew_printf( _XT( "Unable to load file (error #%d: %s)\n" ),
                    code, scew_error_string( code ) );
        if( retVal )
        {
            *retVal = rpmStsDatabaseReadError;
        }

        if( code == scew_error_expat )
        {
            enum XML_Error expat_code = scew_error_expat_code( parser );
            scew_printf( _XT( "Expat error #%d (line %d, column %d): %s\n" ),
                        expat_code,
                        scew_error_expat_line( parser ),
                        scew_error_expat_column( parser ),
                        scew_error_expat_string( expat_code ) );
        }

        /* Frees the SCEW parser, printer, reader and writer. */
        scew_reader_free( reader );
        scew_parser_free( parser );

        return( NULL );
    }
    /* Build the database based on the tree */
    scew_element * theRoot = scew_tree_root( tree );
    if( scew_strcmp( scew_element_name( theRoot ), _XT( "minirpm" ) ) != 0 )
    {
        if( retVal )
        {
            *retVal = rpmStsDatabaseReadError;
        }
        /* Frees the SCEW parser, printer, reader and writer. */
        scew_reader_free( reader );
        scew_parser_free( parser );

        return( NULL );
    }

    if( !newHandle->parse_error )
    {
        newHandle->tree = tree;
        return( newHandle );
    }

    //TODO: Free busted handle
    return( NULL );

}

void
db_close( RpmDataBaseHandle db )
{
    if( !db->dirty )
    {
        /* Release the transaction lock. */
        //rpm_ReleaseLock( db->lock );
        /* No changes were made.  Release the memory associated with
         * the database.*
         */
        //rpm_FreeRpmDatabase(db);
    }
    if( db->writeAccess == 1 )
    {

        /* Generate a new XML version of the database. */
        /* Save the XML tree to a file. */
        scew_writer * writer = scew_writer_file_create( db->database_file );
        if( writer == NULL )
        {
            printf( "Unable to create %s\n", db->database_file );
            return;
        }

        scew_printer * printer = scew_printer_create( writer );
        if( printer == NULL )
        {
            printf( "Unable to create printer\n" );
            return;
        }

        /* We should check for errors here. */
        (void) scew_printer_print_tree( printer, db->tree );
        scew_writer_free( writer );
    }

    if( db->lock_file )
    {
        unlink( db->lock_file );
        free( db->lock_file );
    }

    free( db->root_path );
    free( db->database_file );
    scew_tree_free( db->tree );
    free( db );
}
